package nl.sweet.testsweet;

import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.LinkedList;

import javax.swing.text.NumberFormatter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import junit.framework.TestResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.sun.org.apache.xerces.internal.dom.DocumentImpl;

/**
 * The test sweet xml handler which can generate the xml, write xml and generate the html.
 * @author wessels
 */
public class TestSweetXMLHandler
{
	/** The total time taken for the tests. */
	private double _totalTimeTaken;

	/** The list with results. */
	private LinkedList<TestSweetResult> _testSweetResults;

	/**
	 * Constructor.
	 * @param testSweetResults The test sweet results.
	 * @param totalTimeTaken The total time taken.
	 */
	public TestSweetXMLHandler(LinkedList<TestSweetResult> testSweetResults, double totalTimeTaken)
	{
		_testSweetResults = testSweetResults;
		_totalTimeTaken = totalTimeTaken;
	}

	/**
	 * Generate the XML Document from the results.
	 * @return The XML Document.
	 */
	@SuppressWarnings("unchecked")
	public Document generateXMLDocument()
	{
		Document xmldoc = new DocumentImpl();
		Element root = xmldoc.createElement("TestSweetResults");
		xmldoc.appendChild(root);
		SimpleDateFormat formatter = new SimpleDateFormat();
		root.setAttribute("datetime", formatter.format(Calendar.getInstance().getTime()));
		root.setAttribute("totalTime", twoDecimalDouble(_totalTimeTaken));
		root.setAttribute("count", "" + _testSweetResults.size());

		for (int i = 0; i < _testSweetResults.size(); ++i)
		{
			TestSweetResult result = _testSweetResults.get(i);
			Element resElement = xmldoc.createElement("result");
			resElement.setAttribute("timeTaken", twoDecimalDouble(result.getTimeTaken()));
			resElement.setAttribute("succes", "" + result.isTestSuccesfull());

			// Create the class name element.
			Element classElement = xmldoc.createElement("className");
			classElement.setTextContent(result.getClassName());
			resElement.appendChild(classElement);

			// Create the test name element.
			Element testElement = xmldoc.createElement("testName");
			testElement.setTextContent(result.getTestCaseName());
			resElement.appendChild(testElement);
			
			// Create the std output element.
			Element stdOutputElement = xmldoc.createElement("stdOutput");
			stdOutputElement.setTextContent(result.getOutput());
			resElement.appendChild(stdOutputElement);
			
			// Create the std error element.
			Element stdErrorElement = xmldoc.createElement("stdError");
			stdErrorElement.setTextContent(result.getError());
			resElement.appendChild(stdErrorElement);

			// If the test was not succesfull, add the errors and failures.
			if (!result.isTestSuccesfull())
			{
				// Get the test results.
				TestResult testResult = result.getTestResult();

				// Add the errors if there are any.
				if (testResult.errorCount() > 0)
				{
					Element errorsElement = xmldoc.createElement("errors");
					errorsElement.setAttribute("count", "" + testResult.errorCount());
					Enumeration errors = testResult.errors();
					while (errors.hasMoreElements())
					{
						Object error = errors.nextElement();
						Element errorElement = xmldoc.createElement("error");
						errorElement.setTextContent(error.toString());
						errorsElement.appendChild(errorElement);
					}
					resElement.appendChild(errorsElement);
				}

				// Add the failures if there are any.
				if (testResult.failureCount() > 0)
				{
					Element failuresElement = xmldoc.createElement("errors");
					failuresElement.setAttribute("count", "" + testResult.failureCount());
					Enumeration failures = testResult.failures();
					while (failures.hasMoreElements())
					{
						Object failure = failures.nextElement();
						Element failureElement = xmldoc.createElement("error");
						failureElement.setTextContent(failure.toString());
						failuresElement.appendChild(failureElement);
					}
					resElement.appendChild(failuresElement);
				}
			}

			root.appendChild(resElement);
		}

		return xmldoc;
	}

	/**
	 * Save the XML Document to a output stream.
	 * @param out The outputstream.
	 */
	public void saveXMLToOutputStream(OutputStream out)
	{
		try
		{
			Document xmldoc = generateXMLDocument();

			TransformerFactory tFactory = TransformerFactory.newInstance();
			Transformer transformer = tFactory.newTransformer();
			transformer.setOutputProperty(OutputKeys.METHOD, "xml");
			transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
			transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");

			transformer.transform(new DOMSource(xmldoc), new StreamResult(out));
		}
		catch (TransformerException e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * Save the HTML to a Writer (ex. FileWriter).
	 * @param out The Writer.
	 */
	public void saveHTMLToWriter(Writer out)
	{
		try
		{
			Document xmldoc = generateXMLDocument();
			Source xsltSource = new StreamSource(TestSweetXMLHandler.class
					.getResourceAsStream("TestSweetXMLLayout.xsl"));
			Result result = new StreamResult(out);

			TransformerFactory tFactory = TransformerFactory.newInstance();
			Transformer transformer = tFactory.newTransformer(xsltSource);
			transformer.setOutputProperty(OutputKeys.METHOD, "html");
			transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
			transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");

			transformer.transform(new DOMSource(xmldoc), result);
		}
		catch (TransformerException e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * Generate a string with the html generated from the xml data.
	 * @return A string with the results.
	 */
	public String generateHTMLFromXML()
	{
		StringWriter out = new StringWriter();

		saveHTMLToWriter(out);

		return out.toString();
	}
	
	/**
	 * Verkrijg een double met 2 decimalen.
	 * @param currentValue Waarde welke twee decimalen moet krijgen.
	 * @return De double met 2 decimalen.
	 */
	private String twoDecimalDouble(double currentValue)
	{
		int decimalPlace = 2;
		BigDecimal bd = new BigDecimal(currentValue);
		bd = bd.setScale(decimalPlace, BigDecimal.ROUND_UP);

		NumberFormatter formatter = new NumberFormatter();
		NumberFormat numberInstance = NumberFormat.getNumberInstance();
		numberInstance.setMinimumFractionDigits(2);
		numberInstance.setMaximumFractionDigits(2);
		formatter.setFormat(numberInstance);
		try
		{
			return formatter.valueToString(new Double(currentValue));
		}
		catch (ParseException ex)
		{
			ex.printStackTrace();
		}
		return null;
	}
}
